package org.sgx.jsutil.gen;

import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import org.sgx.jsutil.client.Util;
import org.sgx.jsutil.nativable.JSOProperty;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * generate all required methods for an EditableBean to be a valid PropertyHaver. (adds methods getProperties() and get/setProperty() to an abstract javabean that implements
 * EditableBean
 * 
 * @author sg
 * 
 */
public class NativableGenerator extends Generator {
	static Logger logger = Logger.getLogger("NativableGenerator");

	Set<NativeProperty> props;

	public String generate(TreeLogger logger, GeneratorContext context, String typeName)
			throws UnableToCompleteException {

		props = new HashSet<NativableGenerator.NativeProperty>();

		TypeOracle typeOracle = context.getTypeOracle();
		JClassType objectType = typeOracle.findType(typeName);
		String implTypeName = objectType.getSimpleSourceName() + "Impl";
		String implPackageName = objectType.getPackage().getName();

		ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(implPackageName,
				implTypeName);

		composerFactory.setSuperclass(objectType.getQualifiedSourceName());

		composerFactory.addImport("org.sgx.jsutil.client.JsObject");
		composerFactory.addImport("com.google.gwt.core.client.JavaScriptObject");

		PrintWriter printWriter = context.tryCreate(logger, implPackageName, implTypeName);
		if (printWriter != null) {

			SourceWriter sourceWriter = composerFactory.createSourceWriter(context, printWriter);

			writeMethods(logger, sourceWriter, objectType);

			sourceWriter.commit(logger);

		}
		return implPackageName + "." + implTypeName;
	}

	public void writeMethods(TreeLogger logger, SourceWriter writer, JClassType jt) {

		JMethod[] _mm = jt.getMethods();
		for (int i = 0; i < _mm.length; i++) {
			JMethod m = _mm[i];
			JSOProperty jsop = m.getAnnotation(JSOProperty.class);
			if (jsop != null) {
				NativeProperty prop = new NativeProperty();
				String name = null;
				boolean isField = false;
				if (Util.notNull(jsop.method())) {
					name = jsop.method();
					isField = false;
				} else if (Util.notNull(jsop.field())) {
					name = jsop.field();
					isField = true;
				} else {
					continue;
				}
				prop.setName(name);
				if (isField) {
					prop.setType(NativeProperty.TYPE_FIELD);
				} else {
					prop.setType(NativeProperty.TYPE_METHOD);
					prop.setParamCount(m.getParameters().length);
				}

				// the java type is always the method's return type no matter it is a field or method
				prop.setJavaClass(m.getReturnType().getJNISignature());

				props.add(prop); 
			}
		}

		String jsniClassName = jt.getQualifiedBinaryName(); //.getName(); //.getJNISignature(); //"org/sgx/Class1";

		writer.println("public final native JavaScriptObject toNative()/*-{");
		writer.indent();
		
		
		writer.println("var o = this;//{}; ");
		for (NativeProperty p : props) {
			String name = p.getName();
			if (name == null || name.length() == 0)
				continue;// TODO:log something
			String name1U = name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
			if (p.getType().equals(NativeProperty.TYPE_FIELD)) {

//				writer.println("alert(this.@" + jsniClassName + "Impl::get" + name1U + "()()); ");
				
				writer.println("o['" + name + "'] = this.@" + jsniClassName + "::get" + name1U + "()();" + "");
			}
			
			if (p.getType().equals(NativeProperty.TYPE_METHOD)) {

				boolean useEntry=false;
				
				//writer.println("var self = this; ");
				writer.print("o['" + name + "'] = ");
				if(useEntry)
					writer.print("$entry(");
				writer.print("function(");
				for (int i = 0; i < p.getParamCount(); i++) {
					writer.print("arg" + (i + 1));
					if (i < p.getParamCount() - 1)
						writer.print(", ");					
				}
				writer.println(") {");
				writer.indent();
				writer.print("return o.@" + jsniClassName + "::"+p.getName()+"(*)(");
				//warning: JSNI '*' wildcard used : method overloading not supported
				for (int i = 0; i < p.getParamCount(); i++) {
					writer.print("arg" + (i + 1));
					if (i < p.getParamCount() - 1)
						writer.print(", ");						
				}
				writer.println("); ");
				writer.outdent();
				writer.print("}; ");
				if(useEntry)
					writer.print(")");
				writer.println("; ");
			}
		}

		writer.println("return o; ");
		writer.outdent();
		writer.println("}-*/;");
	}

	private boolean isAssignableFrom(JClassType jt, Class<?> c) {
		try {
			return jt.getOracle().findType(c.getName()).isAssignableFrom(jt);
		} catch (Exception e) {
			return false;
		}
	}

	public static class NativeProperty {
		public static final String TYPE_FIELD = "field", TYPE_METHOD = "method";
		/** one of TYPE_* constants */
		String type;
		/** property name */
		String name;
		/** the java field type or method return type */
		String javaClass;
		/** number of method parameters */
		int paramCount;

		public String getType() {
			return type;
		}

		public void setType(String type) {
			this.type = type;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getJavaClass() {
			return javaClass;
		}

		public void setJavaClass(String javaClass) {
			this.javaClass = javaClass;
		}

		public int getParamCount() {
			return paramCount;
		}

		public void setParamCount(int paramCount) {
			this.paramCount = paramCount;
		}

	}

}